// locutor
// (c) 2009, Normen Müller, normen.mueller@gmail.com  
// $Id: Parser.scala 86 2010-05-11 21:32:13Z normen.mueller $
package locutor.svn.extdef.parsing

import java.net.URI

import scala.util.parsing.combinator._
import scala.util.parsing.combinator.syntactical._

import scaup.Views._

/** A SVN externals definitions parser.
 * 
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 */
private [extdef] class Parser extends StdTokenParsers with ImplicitConversions {
  /* Fill in abstract defs
   */
  type Tokens = Lexer
  
  val lexical = new Tokens
  
  /* Regular expression for extracting URL@PEG
   */
  private val regex = """(.*)@(.*)""".r
  
  /* We have to distinguish absolute from relative URIs, cause "^/..." is not a valid URI (cf. isValidURI and u.toURI, respectively)
   */                               
  private val svnurl = "^file://.*|^https://.*|^http://.*|^svn://.*|^svn\\+ssh://.*|^../.*|^/.*|^//.*|^\\^/.*".r.pattern
  
  private val svnurlabs = "^file://.*|^https://.*|^http://.*|^svn://.*|^svn\\+ssh://.*".r.pattern
  
  private val svnurlrel = "^../.*|^/.*|^//.*|^\\^/.*".r.pattern
  
  /* Define the SVN 1.5 grammar
   */
  lazy val extdef_stmt_15 = opt(rev_stmt) ~ url_stmt ~ target_stmt ^^ {
    case Some(rev) ~ Some(url) ~ Some(target) => Some(ExtDef(rev, url._1, url._2, target))
    case None ~ Some(url) ~ Some(target)      => Some(ExtDef(-1, url._1, url._2, target))
    case _                                    => None
  }
  
  /* Define the SVN 1.4 grammar
   */
  lazy val extdef_stmt_14 = target_stmt ~ opt(rev_stmt) ~ url_stmt ^^ {
    case Some(target) ~ Some(rev) ~ Some(url) => Some(ExtDef(rev, url._1, url._2, target))
    case Some(target) ~ None ~ Some(url)      => Some(ExtDef(-1, url._1, url._2, target))
    case _                                    => None
  }
  
  lazy val rev_stmt = numericLit ^^ { _.toLong }
  
  lazy val url_stmt = stringLit ^^ {
    case u if svnurlabs.matcher(u).matches && isValidURI(u) => Some(split(u))
    case u if svnurlrel.matcher(u).matches                  => Some(split(u))
    case _                                                  => None
  }
  
  /* With respect to
   * <a href="http://svnbook.red-bean.com/en/1.4/svn-book.html#svn.advanced.externals">externals definitions</a>
   * of Subversion 1.4, an externals definition can only point to directories, not files, and the externals definition
   * cannot point to relative paths (paths like ../../skins/myskin).
   * 
   * Same holds for Subversion 1.5
   * (cf. <a href="http://svnbook.red-bean.com/en/1.5/svn-book.html#svn.advanced.externals">Externals Definitons</a>).
   */
  lazy val target_stmt = stringLit ^^ {
    case p if !(svnurl.matcher(p).matches) => Some(p)
    case _                                 => None
  }
  
  private def isValidURI(u: String) = try { u.toURI; true } catch { case ex: java.net.URISyntaxException => false  }
  
  /* Extract peg revision
   */
  private def split(s: String): (String, Long) = s match {
    case regex(before, after) => (before,after.toLong) // Nice group-based matching, eh? ;-)
    case _                    => (s,-1)
  }  
}
